package com.luhe.midi.mtrk;

import com.luhe.midi.helper.ByteUtil;
import com.luhe.midi.helper.MidiUtil;
import com.luhe.midi.mtrk.event.Event;
import com.luhe.midi.mtrk.event.midievent.FinalEvent;
import com.luhe.midi.mtrk.event.midievent.KeyPressEvent;
import com.luhe.midi.mtrk.event.midievent.KeyRealseEvent;
import org.apache.log4j.Logger;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 轨道：
 * 4D 54 72 6B     # Mtrk标志
 * 00 00 00 25     # 本块Mtrk数据段大小，后面所有事件的长度
 * ...
 * 00 FF 2F 00     # 结束事件
 */
public class MTrk {
    Logger logger = Logger.getLogger(this.getClass().getName());


    //事件
    private List<Event> eventList = new ArrayList<Event>();

    //添加事件
    public void addEvent(Event event) {
        this.eventList.add(event);
    }

    //获取事件列表
    public List<Event> getEventList() {
        return eventList;
    }

    //字节长度
    int len = 0;

    //曲目名字
    public String name;

    //内部字节
    byte innerByte[];

    //计算轨道的长度
    public int getLen() throws Exception {
        ByteArrayOutputStream eventByteArrayOutputStream = new ByteArrayOutputStream();
        for (Event event : this.eventList) event.writeOutputStream(eventByteArrayOutputStream);
        this.len = eventByteArrayOutputStream.size();
        eventByteArrayOutputStream.close();
        return this.len;
    }

    //写出输出流
    public void writeOutputStream(OutputStream outputStream) throws Exception {

        //先暂存到字节输出流
        ByteArrayOutputStream eventByteArrayOutputStream = new ByteArrayOutputStream();

        //Mtrk头部
        eventByteArrayOutputStream.write('M');
        eventByteArrayOutputStream.write('T');
        eventByteArrayOutputStream.write('r');
        eventByteArrayOutputStream.write('k');
        logger.info("写出头部：M T r k");


        // 计算并写出mtrk长度
        byte track_len[] = MidiUtil.intToByteArray(this.getLen());
        eventByteArrayOutputStream.write(track_len);
        logger.info("写轨道长：" + ByteUtil.printByte(track_len) + " 长度：" + this.len);

        //写事件
        for (Event event : this.eventList) {
            byte[] bytes = event.writeOutputStream(eventByteArrayOutputStream);
            logger.info("写出事件：" + event.getClass().getName() + "：" + ByteUtil.printByte(bytes));
        }


        //得到结果
        logger.info("事件写出完毕！");
        outputStream.write(eventByteArrayOutputStream.toByteArray());
        eventByteArrayOutputStream.close();
    }

    public MTrk read(InputStream inputStream) throws Exception {

        //清空事件表
        this.eventList = new ArrayList<>();

        //读头部MTrk：4d	54	72	6b
        byte mtrk[] = new byte[4];
        inputStream.read(mtrk);
        logger.info("轨道头部：" + ByteUtil.printByte(mtrk));

        //读轨道字节长度：00	00	00	00
        byte len[] = new byte[4];
        inputStream.read(len);
        this.len = MidiUtil.bytesToInt(len, 0, 4);
        logger.info("轨道长度：" + ByteUtil.printByte(len) + ",共" + this.len + "字节");

        //当前事件种类
        int currentType = 0;


        //读事件
        while (true) {

            //读动态时长
            byte[] tick_bytes = MidiUtil.readDynamicIntBytes(inputStream);
            int tick = MidiUtil.readDynamicInt(tick_bytes, 0, tick_bytes.length);

            //读事件类型:1字节
            byte event[] = new byte[1];
            inputStream.read(event);


            //!读到上一个事件 00~7F
            if (!(MidiUtil.getHeight4((byte) event[0]) >= 0 && MidiUtil.getHeight4((byte) event[0]) <= 7)) {
                currentType = MidiUtil.getHeight4((byte) event[0]);
            }

            //读到松开某键
            if (currentType == 8) {

                //2个参数
                byte b[] = new byte[2];
                inputStream.read(b);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byteArrayOutputStream.write(tick_bytes);
                byteArrayOutputStream.write(event);
                byteArrayOutputStream.write(b);
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                KeyRealseEvent keyRealseEvent = (KeyRealseEvent) new KeyRealseEvent().readInputStream(byteArrayInputStream);
                this.eventList.add(keyRealseEvent);
            }

            //读到按下某键
            if (MidiUtil.getHeight4((byte) event[0]) == 9) {
                //2个参数
                byte b[] = new byte[2];
                inputStream.read(b);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byteArrayOutputStream.write(tick_bytes);
                byteArrayOutputStream.write(event);
                byteArrayOutputStream.write(b);
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                KeyPressEvent keyPressEvent = (KeyPressEvent) new KeyPressEvent().readInputStream(byteArrayInputStream);
                this.eventList.add(keyPressEvent);
            }

            //Ax 触后音符，2个参数
            if (MidiUtil.getHeight4(event[0]) == 10) {
                byte b[] = new byte[2];
                inputStream.read(b);
                logger.info("触后音符");
            }
            //Bx 控制器事件，2个参数
            if (MidiUtil.getHeight4(event[0]) == 11) {
                byte b[] = new byte[2];
                inputStream.read(b);
                logger.info("控制器事件");
            }
            //Cx 改变乐器，1个参数
            if (MidiUtil.getHeight4(event[0]) == 12) {

                byte b[] = new byte[1];
                inputStream.read(b);
                logger.info("改变乐器");
            }
            //Dx 触后通道，1个参数
            if (MidiUtil.getHeight4(event[0]) == 13) {

                byte b[] = new byte[1];
                inputStream.read(b);
                logger.info("触后通道");
            }
            //Ex 滑音，2个参数
            if (MidiUtil.getHeight4(event[0]) == 14) {
                byte b[] = new byte[2];
                inputStream.read(b);
                logger.info("滑音");
            }
            //F0系统码
            if (event[0] == (byte) 0xf0) {
                byte[] k = MidiUtil.readDynamicIntBytes(inputStream);
                int data_len = MidiUtil.readDynamicInt(k, 0, k.length);
                logger.info("F0系统码,数据长度：" + data_len);
                byte[] dat = new byte[data_len];
                inputStream.read(dat);

            }
            //FF 系统码，   <时间> + FF + <1字节 种类> + <动态字节数据长度> + <数据>
            if (event[0] == (byte) 0xff) {

                //读FF系统码种类
                byte FFType[] = new byte[1];
                inputStream.read(FFType);

                //歌曲标题
                if (FFType[0] == (byte) 0x03) {
                    byte[] FF_data_len_bytes = MidiUtil.readDynamicIntBytes(inputStream);
                    int FF_data_len = MidiUtil.readDynamicInt(FF_data_len_bytes, 0, FF_data_len_bytes.length);
                    logger.info("系统码 标题,数据长度：" + FF_data_len);
                    byte[] FFData = new byte[FF_data_len];
                    inputStream.read(FFData);
                    String name = new String(FFData, "GBK");
                    logger.info("曲目名字：" + name);
                    this.name = name;

                }
                //乐器名字
                if (FFType[0] == (byte) 0x04) {
                    byte[] FF_data_len_bytes = MidiUtil.readDynamicIntBytes(inputStream);
                    int FF_data_len = MidiUtil.readDynamicInt(FF_data_len_bytes, 0, FF_data_len_bytes.length);
                    byte[] FFData = new byte[FF_data_len];
                    inputStream.read(FFData);
                    logger.info("系统码 乐器名字 数据长度：" + FF_data_len + "," + ByteUtil.printByte(FFData));
                    String instrumentName = new String(FFData, "GBK");
                    logger.info("乐器名：" + instrumentName);

                }
                //速度
                else if (FFType[0] == (byte) 0x51) {
                    byte[] FF_data_len_bytes = MidiUtil.readDynamicIntBytes(inputStream);
                    int FF_data_len = MidiUtil.readDynamicInt(FF_data_len_bytes, 0, FF_data_len_bytes.length);
                    byte[] FFData = new byte[FF_data_len];
                    logger.info("系统码 速度 数据长度：" + FF_data_len + "," + ByteUtil.printByte(FFData));
                    inputStream.read(FFData);

                }
                //节拍
                else if (FFType[0] == (byte) 0x58) {
                    byte[] FF_data_len_bytes = MidiUtil.readDynamicIntBytes(inputStream);
                    int FF_data_len = MidiUtil.readDynamicInt(FF_data_len_bytes, 0, FF_data_len_bytes.length);

                    byte[] FFData = new byte[FF_data_len];
                    logger.info("系统码 节拍 数据长度：" + FF_data_len + "," + ByteUtil.printByte(FFData));
                    inputStream.read(FFData);

                }
                //调号
                else if (FFType[0] == (byte) 0x59) {
                    byte[] FF_data_len_bytes = MidiUtil.readDynamicIntBytes(inputStream);
                    int FF_data_len = MidiUtil.readDynamicInt(FF_data_len_bytes, 0, FF_data_len_bytes.length);
                    byte[] FFData = new byte[FF_data_len];
                    inputStream.read(FFData);
                    logger.info("系统码 调号 数据长度：" + FF_data_len + "," + ByteUtil.printByte(FFData));
                    int key = FFData[0];
                    int major = FFData[1];
                    logger.info("调式：" + key + " 大调：" + major);
                }
                //结尾
                else if (FFType[0] == (byte) 0x2F) {


                    byte[] FFData = new byte[1];
                    inputStream.read(FFData);
                    logger.info("读到结尾事件:" + ByteUtil.printByte(tick_bytes) + " " + tick + ", "
                            + ByteUtil.printByte(event) + " " + ByteUtil.printByte(FFType) + " " + ByteUtil.printByte(FFData));
                    FinalEvent finalEvent = new FinalEvent();
                    finalEvent.tick = tick;
                    this.eventList.add(finalEvent);
                    break;
                }
                //其它FF系统事件
                else {

                    byte[] k = MidiUtil.readDynamicIntBytes(inputStream);
                    int data_len = MidiUtil.readDynamicInt(k, 0, k.length);
                    logger.info("系统码 其它 数据长度：" + data_len);
                    byte[] dat = new byte[data_len];
                    inputStream.read(dat);
                }


            }
        }

        return this;
    }

}
